package com.ce.framework.filter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

public class DdosOffFilter implements Filter{
	final static int MOD_IP = 0;//按请求IP进行判断
	final static int MOD_SESSION = 1;//按请求的SESSIONID进行判断
	final static int MOD_IP_URI = 2;//按用户的ID进行判断。暂不能使用
	final static int MOD_SESSION_URI = 3;//按用户的ID进行判断。暂不能使用
	final static int MOD_IP_URL = 2;//按用户的ID进行判断。暂不能使用
	final static int MOD_SESSION_URL = 3;//按用户的ID进行判断。暂不能使用
	
	private Logger log = Logger.getLogger(DdosOffFilter.class);
	
	/** URI过滤正则表达式，默认只支持*代表[^/]*，**代表.*，如果以reg:开头，后边为Java正则表达式内容 */
	private Pattern reg = Pattern.compile("/.*");
	/** URI过滤正则表达式，默认只支持*代表[^/]*，**代表.*，如果以reg:开头，后边为Java正则表达式内容 */
	private Set<String> uriWhiteList = new HashSet<String>();
	/** 防攻击模式，默认为按IP进行过滤，但当存在子网络时，会有多用户用同一IP现象，此时可采用MOD_SESSION模式，按SESSIONID过滤 */
	private int mode = MOD_IP;
	/** 判断攻击的时间间隔，单位为秒 */
	private int pageInterval = 1;
	/** 判断攻击的在指定时间间隔内的请求次数 */
	private int pageCount = 3;
	/** 判定为攻击后禁止访问的时间，单位为秒 */
	private int blockingPeriod = 10;
	/** 判定为攻击后显示的消息，如果指定errorPage，则此项无效！ */
	private String errorMsg = "<h1>Too fast for your visit, please access later!</h1>";
	/** 判定为攻击后跳转的页面 */
	private String errorPage;
	/** 非法访问日志 */
	private PrintWriter logFile;

	public void init(FilterConfig conf) throws ServletException {
		//配置参数初始化
		String pat = conf.getInitParameter("pattern");
		String uwl = conf.getInitParameter("uriWhiteList");
		String mod = conf.getInitParameter("mode");
		String pi = conf.getInitParameter("pageInterval");
		String pc = conf.getInitParameter("pageCount");
		String bp = conf.getInitParameter("blockingPeriod");
		String em = conf.getInitParameter("errorMsg");
		String ep = conf.getInitParameter("errorPage");
		String lf = conf.getInitParameter("logFile");
		if(pat!=null){//URL过滤规则
			if(pat.startsWith("reg:")){
				pat = pat.substring(4);
			}
			else{
				pat = pat.replace("**", ".*").replace("*", "[^/]*");
			}
			reg = Pattern.compile(pat);
		}
		if(uwl!=null){//URI白名单
			String[] ary = uwl.split("[,|;|:|\\|\\s]+");
			for(String uri:ary){
				uriWhiteList.add(uri);
			}
		}
		if(mod!=null){//防攻击模式
			if("MOD_IP".equalsIgnoreCase(mod))
				mode = MOD_IP;
			else if("MOD_SESSION".equalsIgnoreCase(mod))
				mode = MOD_SESSION;
			else if("MOD_IP_URL".equalsIgnoreCase(mod))
				mode = MOD_IP_URL;
			else if("MOD_SESSION_URL".equalsIgnoreCase(mod))
				mode = MOD_SESSION_URL;
			else if("MOD_IP_URI".equalsIgnoreCase(mod))
				mode = MOD_IP_URI;
			else if("MOD_SESSION_URI".equalsIgnoreCase(mod))
				mode = MOD_SESSION_URI;
		}
		if(pi!=null){//判断攻击的时间间隔
			try {
				pageInterval = Integer.parseInt(pi);
			} catch (NumberFormatException e) {
			}
		}
		if(pc!=null){//判断攻击的在指定时间间隔内的请求次数
			try {
				pageCount = Integer.parseInt(pc);
			} catch (NumberFormatException e) {
			}
		}
		if(bp!=null){//判定为攻击后禁止访问的时间
			try {
				blockingPeriod = Integer.parseInt(bp);
			} catch (NumberFormatException e) {
			}
		}
		if(em!=null){//判定为攻击后显示的消息
			errorMsg = em;
		}
		if(ep!=null){//判定为攻击后跳转的页面
			errorPage = ep;
		}
		String logFilePath = "ddosoff.log";
		if(lf!=null){//判定为攻击后跳转的页面
			logFilePath = lf;
		}
		try {
			logFile = new PrintWriter(new FileWriter(logFilePath),true);
			log("log file is "+new File(logFilePath).getCanonicalPath());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private HashMap<String,HitNode> hitList = new HashMap<String,HitNode>(3097);
	public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException,
			ServletException {
		HttpServletRequest request = (HttpServletRequest) arg0;
		HttpServletResponse response = (HttpServletResponse) arg1;
		String uri = request.getRequestURI();
		String path = request.getContextPath();
		int pos = path.length()<=1?0:path.length();
		if(!reg.matcher(uri).matches() || uriWhiteList.contains(uri.substring(pos))){
			arg2.doFilter(arg0, arg1);
			return;
		}
		//System.out.println("===========  "+uri);
		boolean forbidden = false;
		long curr = System.currentTimeMillis();
		String key = getRequestKey(request);
		HitNode n = hitList.get(key);
		/* 
		 * 整体思路如下：
		 * 首先根据请求获取客户的特征值，根据特征值在HashMap中取到对应客户的访问历史
		 * 访问历史中有三个对象，lastCntTime存储上次计时时间，单位为毫秒，
		 * hitCount是从上次计时时间开始到现在的点击次数,block是指是否被判断为攻击
		 * 当判定为攻击时，lastCntTime中存储最后一次访问时间，
		 * 当前时间-最后一次访问时时间>禁止访问时间时，表示用户可以正常访问，否则继续禁止访问
		 */
		if(n!=null){
			if(n.hitCount>=pageCount){
				//如果用户已经过了禁止访问时间，将用户的访问次数和计时时间重置，block置为false
				if(n.block&&curr-n.lastCntTime>blockingPeriod*1000){
					n.block = false;
					n.hitCount = 0;
				}
				else{
					//当被判断为攻击时，记录日志
					log(request.getRemoteAddr()+" has visited "+(n.hitCount+1)+" times in "+pageInterval+" seconds.");
					forbidden = true;
					n.block = true;
				}
				n.hitCount++;
				n.lastCntTime = curr;//更新黑名单中的最后访问时间
			}
			else{//正常情况下访问次数增加
				if(curr-n.lastCntTime<pageInterval*1000){
					n.hitCount++;
				}
				else{//如果一个考察期结束，则进入下一个考核期
					n.hitCount = 1;
					n.lastCntTime = curr;
				}
			}
		}
		else{//如果用户第一次访问，直接记录用户首次访问信息
			hitList.put(key, new HitNode(curr,1));
		}
		if(forbidden){
			this.showErrorPage(response);
			return;
		}
		arg2.doFilter(arg0, arg1);
	}
	
	/**
	 * 获取请求的关键特征值
	*/
	private String getRequestKey(HttpServletRequest request){
		if(mode == MOD_IP){
			return request.getRemoteAddr();
		}
		else if(mode == MOD_SESSION){
			return request.getSession().getId();
		}
		else if(mode == MOD_IP_URL){
			return request.getRemoteAddr()+"||"+request.getRequestURL();
		}
		else if(mode == MOD_SESSION_URL){
			return request.getSession().getId()+"||"+request.getRequestURL();
		}
		else if(mode == MOD_IP_URI){
			return request.getRemoteAddr()+"||"+request.getRequestURI();
		}
		else if(mode == MOD_SESSION_URI){
			return request.getSession().getId()+"||"+request.getRequestURI();
		}
		else{
			return request.getRemoteAddr();
		}
	}
	
	/**
	 * 当判定为攻击时显示错误信息页面
	*/
	private void showErrorPage(HttpServletResponse response){
		try {
			if(errorPage!=null&&errorPage.length()>0){
				response.sendRedirect(errorPage);
			}
			else{
				response.setContentType("text/html; charset=UTF-8"); // 设置 content-type
				response.setCharacterEncoding("UTF-8");  // 设置响应数据编码格式 (输出)
				response.getWriter().println(errorMsg);
				response.getWriter().close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	/**
	 * 打印信息
	*/
	private void log(String msg){
		try {
			log.info(msg);
		} catch (Exception e) {
		}
		logFile.println(sdf.format(new Date())+" "+msg);
	}

	public void destroy() {
	}

}
class HitNode{
	public HitNode(long lastCntTime, int hitCount) {
		this.lastCntTime = lastCntTime;
		this.hitCount = hitCount;
		this.block = false;
	}
	long lastCntTime;
	int hitCount;
	boolean block;
}
